package org.kiwi.data.interceptor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.kiwi.data.DbShardingStrategyHolder;
import org.kiwi.data.annotation.DbSharding;
import org.kiwi.data.sharding.DbShardingStrategy;
import org.kiwi.data.sharding.DefaultDbShardingStrategy;
import org.kiwi.data.util.MethodUtil;
import org.kiwi.data.util.SPELUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * Created by jack on 16/12/17.
 */
public class DbShardingInterceptor implements MethodInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(DbShardingInterceptor.class);

    @Autowired
    private ApplicationContext applicationContext;

    private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();

    private Map<String, DbShardingStrategy> dbShardingStrategies;

    public void setDbShardingStrategies(Map<String, DbShardingStrategy> dbShardingStrategies) {
        this.dbShardingStrategies = dbShardingStrategies;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        String debugInfo = "[" + invocation.toString() + "]";

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("enter into db sharding interceptor {}", debugInfo);
        }

        Method realMethod = MethodUtil.getRealMethod(invocation);

        DbSharding dbShardingAnnotation = realMethod.getAnnotation(DbSharding.class);

        if (dbShardingAnnotation != null) {
            DbShardingStrategy dbShardingStrategy = this.dbShardingStrategies.get(dbShardingAnnotation.strategyName());

            Object dbShardParameter = SPELUtil.getValue(
                    this.parameterNameDiscoverer.getParameterNames(realMethod),
                    invocation.getArguments(),
                    dbShardingAnnotation.expression(),
                    this.applicationContext);

            if (dbShardingStrategy == null) {
                dbShardingStrategy = DefaultDbShardingStrategy.getInstance();
            }

            DbShardingStrategyHolder.setDbGroupName(dbShardingStrategy.determineDbGroupName(dbShardParameter));

        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("db sharding annotation is null {}", debugInfo);
            }
        }

        try {
            return invocation.proceed();
        } catch (Exception e) {
            LOGGER.error("invoke db sharding interceptor has some problem.", e);
            throw e;
        } finally {
            DbShardingStrategyHolder.clear();

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("leave from db sharding interceptor {}", debugInfo);
            }
        }
    }
}
